/* Copyright (c) 2022 渝州大数据实验室
 *
 * Lanius is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *
 *     http://license.coscl.org.cn/MulanPSL2
 *
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.yzbdl.lanius.orchestrate.serv.utils.cron;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.yzbdl.lanius.orchestrate.serv.enums.WeekEnum;
import org.yzbdl.lanius.orchestrate.common.utils.cron.CronUtils;

/**
 * Cron表达式解析器
 *
 * @author jinchunzhao@yzbdl.ac.cn
 * @date 2022-04-25 13:38
 */
public class CronParserUtil {

    /**
     * cron表达式时间中文集合
     */
    private final static List<String> CRON_TIME_CN_LIST = Arrays.asList("秒", "分钟", "小时", "天", "周", "月", "年");

    /**
     * 24小时
     */
    private final static Integer HOURS = 24;

    /**
     * 时间尺度
     */
    private final static Integer TIME_SCALE = 60;

    /**
     * 错误消息
     */
    private final static String CRON_BLANK = "Cron表达式为空";

    /**
     * 错误消息
     */
    private final static String CRON_ERROR = "Cron表达式格式不正确";

    /**
     * Cron表达式星号通配符
     */
    private final static String ASTERISK_WILDCARD = "*";

    /**
     * Cron表达式问号通配符
     */
    private final static String QUESTION_MARK_WILDCARD = "?";

    /**
     * Cron表达式值中的斜杠
     */
    private final static String CRON_SLASH = "/";

    /**
     * Cron表达式值中的逗号
     */
    private final static String CRON_COMMA = ",";

    /**
     * Cron表达式值中的L
     */
    private final static String CRON_L = "L";

    /**
     * Cron表达式值中的W
     */
    private final static String CRON_W = "W";

    /**
     * Cron表达式值中的LW
     */
    private final static String CRON_LW = "LW";

    /**
     * Cron表达式值中的#
     */
    private final static String CRON_WELL = "#";

    /**
     * Cron表达式值中的-
     */
    private final static String CRON_CROSS = "-";

    /**
     * Cron表达式转换为中文语义
     *
     * @param cronExpression
     *        cron表达式
     * @return
     *        中文语义
     */
    public static String translateToCn(String cronExpression) {
        try {
            if (StringUtils.isBlank(cronExpression)) {
                return CRON_BLANK;
            }
            if (!CronUtils.isValid(cronExpression)) {
                return CRON_ERROR;
            }
            String[] tempCronArr = cronExpression.split(" ");
            if (Objects.isNull(tempCronArr) || tempCronArr.length == 0) {
                return CRON_ERROR;
            }

            int tempCronArrLength = tempCronArr.length;
            if (tempCronArrLength < 6 || tempCronArrLength > 7) {
                return CRON_ERROR;
            }

            // 本地缓存
            StringBuilder sb = new StringBuilder();

            StringBuilder errorMsg = new StringBuilder();

            parserYear(tempCronArrLength, sb, tempCronArr);

            parserMonth(sb, tempCronArr);

            parserWeek(sb, tempCronArr,errorMsg);
            if (errorMsg.length() > 0){
                return cronExpression + errorMsg.toString();
            }

            parserDay(sb, tempCronArr);
            parserHours(sb, tempCronArr);
            parserMinutes(sb, tempCronArr);
            parserSeconds(sb, tempCronArr);

            if (sb.toString().length() > 0) {
                sb.append("，执行一次");
            } else {
                sb.append("抱歉，我还在学习中...");
            }
            return sb.toString();
        } catch (Exception e) {
            return cronExpression + "（抱歉，我还在学习中...）";
        }
    }

    /**
     * 解析年
     *
     * @param tempCronArrLength
     *        临时Cron表达式数组长度
     * @param sb
     *        翻译信息
     * @param tempCronArr
     *        临时Cron数组
     */
    private static void parserYear(int tempCronArrLength, StringBuilder sb, String[] tempCronArr) {
        if (tempCronArrLength == 7) {
            // 解析年
            String year = tempCronArr[6];
            if (Objects.equals(year, ASTERISK_WILDCARD) || Objects.equals(year, QUESTION_MARK_WILDCARD)) {
                sb.setLength(0);
            } else if (year.contains(CRON_SLASH)) {
                String[] yearArr = year.split(CRON_SLASH);
                sb.append("从").append(yearArr[0]).append("年开始，每隔").append(yearArr[1]).append(CRON_TIME_CN_LIST.get(6));
            } else if (year.contains(CRON_CROSS)) {
                String[] yearArr = year.split(CRON_CROSS);
                sb.append("从").append(yearArr[0]).append(CRON_TIME_CN_LIST.get(6)).append("至").append(yearArr[1])
                    .append(CRON_TIME_CN_LIST.get(6)).append("，");
            } else {
                sb.append(year).append(CRON_TIME_CN_LIST.get(6));
            }
        }
    }

    /**
     * 解析月
     * <P>
     *     主要解析： /
     * </P>
     *
     * @param sb
     *        翻译信息
     * @param tempCronArr
     *        临时Cron数组
     */
    private static void parserMonth(StringBuilder sb, String[] tempCronArr) {
        // 解析月 主要解析： /
        String month = tempCronArr[4];
        if (Objects.equals(month, ASTERISK_WILDCARD) || Objects.equals(month, QUESTION_MARK_WILDCARD)) {
            return;
        }
        if (month.contains(CRON_SLASH)) {
            String[] monthArr = month.split(CRON_SLASH);
            sb.append("从").append(monthArr[0]).append("月开始，每隔").append(monthArr[1]).append(CRON_TIME_CN_LIST.get(5));
        } else {
            sb.append("每年").append(month).append(CRON_TIME_CN_LIST.get(5));
        }
    }

    /**
     * 解析周
     * <P>
     *     主要解析： , - 1~7/L 1L~7l
     * </P>
     *
     * @param sb
     *        翻译信息
     * @param tempCronArr
     *        临时Cron数组
     * @return 消息
     */
    private static void parserWeek(StringBuilder sb, String[] tempCronArr,StringBuilder errorMsg) {
        // 解析周 主要解析： , - 1~7/L 1L~7l
        String dayOfWeek = tempCronArr[5];

        if (Objects.equals(dayOfWeek, ASTERISK_WILDCARD) || Objects.equals(dayOfWeek, QUESTION_MARK_WILDCARD)) {
            return ;
        }

        // 逗号隔开多个数字
        if (dayOfWeek.contains(CRON_COMMA)) {
            sb.append("每周的第").append(dayOfWeek).append(CRON_TIME_CN_LIST.get(3));
        } else if (dayOfWeek.contains(CRON_L) && dayOfWeek.length() > NumberUtils.INTEGER_ONE) {
            // 1L-7L
            String weekNum = dayOfWeek.split(CRON_L)[0];
            String weekName = judgeWeek(weekNum);
            sb.append("每月的最后一周的");
            sb.append(weekName);
        } else if (dayOfWeek.contains(CRON_WELL)) {
            String[] weekArr = dayOfWeek.split(CRON_WELL);
            String weekName = WeekEnum.matchName(weekArr[0]);
            sb.append("在这个月的第").append(weekArr[1]).append("个").append(weekName);
        } else {
            // 1-7/L
            if (Objects.equals(dayOfWeek, CRON_L)) {
                // L转换为7
                dayOfWeek = "7";
            }
            int weekNums = Integer.parseInt(dayOfWeek);

            if (weekNums < 0 || weekNums > 7) {
                errorMsg.append("Cron表达式有误，dayOfWeek 数字应为1-7");
                return;
            }
            sb.append("每周的");
            String weekName = judgeWeek(dayOfWeek);
            sb.append(weekName);
        }
    }

    /**
     * 解析日
     * <P>
     *     主要解析：  / # L W
     * </P>
     *
     * @param sb
     *        翻译信息
     * @param tempCronArr
     *        临时Cron数组
     */
    private static void parserDay(StringBuilder sb, String[] tempCronArr) {
        // 解析日 主要解析： / # L W
        String dayOfMonth = tempCronArr[3];

        if (Objects.equals(dayOfMonth, ASTERISK_WILDCARD) || Objects.equals(dayOfMonth, QUESTION_MARK_WILDCARD)) {
            if (sb.toString().length() == 0 || tempCronArr.length == 7) {
                sb.append("每").append(CRON_TIME_CN_LIST.get(3));
            }
            return;
        }

        if (dayOfMonth.contains(CRON_SLASH)) {
            String[] dayOfMonthArr = dayOfMonth.split(CRON_SLASH);
            sb.append("每周从第").append(dayOfMonthArr[0]).append("天开始，每").append(dayOfMonthArr[1])
                .append(CRON_TIME_CN_LIST.get(3));
        } else if (Objects.equals(dayOfMonth, CRON_L)) {
            // 每月的最后一天
            sb.append("每月的最后一天");
        } else if (Objects.equals(dayOfMonth, CRON_W)) {
            // TODO 此业务有时间在完善
            sb.append(dayOfMonth);
        } else if (Objects.equals(dayOfMonth, CRON_WELL)) {
            String[] dayOfMonthArr = dayOfMonth.split(CRON_WELL);
            // 前面数字表示周几
            String weekNum = dayOfMonthArr[0];
            // 后面的数字表示第几周
            String weekOfMonth = dayOfMonthArr[1];
            String weekName = judgeWeek(weekNum);
            sb.append("每月第").append(weekOfMonth).append(CRON_TIME_CN_LIST.get(5)).append(weekName);
        } else if (Objects.equals(dayOfMonth, CRON_LW)) {
            sb.append("每月的最后一个工作日");
        } else if (dayOfMonth.contains(CRON_CROSS)) {
            String[] dayArr = dayOfMonth.split(CRON_CROSS);
            sb.append("每月底前").append(dayArr[1]).append("天");
        } else if (dayOfMonth.endsWith(CRON_W)) {
            String[] dayArr = dayOfMonth.split(CRON_W);
            sb.append("每月").append(dayArr[0]).append("日最近的那个工作日(周一至周五), ");
        } else {
            sb.append("每月第").append(dayOfMonth).append(CRON_TIME_CN_LIST.get(3));
        }

    }

    /**
     * 解析小时
     * <P>
     *     主要解析： /
     * </P>
     *
     * @param sb
     *        翻译信息
     * @param tempCronArr
     *        临时Cron数组
     */
    private static void parserHours(StringBuilder sb, String[] tempCronArr) {
        // 解析小时， 主要解析： /
        String hours = tempCronArr[2];
        String minutes = tempCronArr[1];
        String seconds = tempCronArr[0];


        if (Objects.equals(hours, ASTERISK_WILDCARD)){
            sb.append("每").append(CRON_TIME_CN_LIST.get(2));
        } else if (hours.contains(CRON_SLASH)) {
            sb.append(appendTimeGapInfo(hours, HOURS, 2));
        } else {
            if (!(sb.toString().length() > 0)) {
                // , 直接拼接
                sb.append("每天").append(hours).append("时");
            } else {
               if (StringUtils.isNumeric(hours)){
                   if (StringUtils.isNumeric(minutes) && StringUtils.isNumeric(seconds)){
                       if (hours.length() == 1){
                           sb.append("0").append(hours).append(":");
                       }else {
                           sb.append(hours).append(":");
                       }
                       return;
                   }

               }
                sb.append(hours).append("时");
            }
        }

    }

    /**
     * 解析Minutes
     * <P>
     *     主要解析： /
     * </P>
     *
     * @param sb
     *        翻译信息
     * @param tempCronArr
     *        临时Cron数组
     */
    private static void parserMinutes(StringBuilder sb, String[] tempCronArr) {
        // 解析Minutes 主要解析： /
        String hours = tempCronArr[2];
        String minutes = tempCronArr[1];
        String seconds = tempCronArr[0];

        if (Objects.equals(minutes, ASTERISK_WILDCARD)){
            sb.append("每").append(CRON_TIME_CN_LIST.get(1));
        } else if (minutes.contains(CRON_SLASH)) {
            sb.append(appendTimeGapInfo(minutes, TIME_SCALE, 1));
        }  else if (minutes.contains(CRON_CROSS)) {
            String[] minuteArr = minutes.split(CRON_CROSS);
            sb.append(minuteArr[0]).append(CRON_TIME_CN_LIST.get(1)).append("到").append(minuteArr[1])
                .append(CRON_TIME_CN_LIST.get(1)).append("每分钟");
        } else {
            if (StringUtils.isNumeric(minutes)){
                if (StringUtils.isNumeric(hours) && StringUtils.isNumeric(seconds)){
                    if (minutes.length() == 1){
                        sb.append("0").append(minutes).append(":");
                    }else {
                        sb.append(minutes).append(":");
                    }
                    return;
                }
            }
            sb.append(minutes).append("分");

        }

    }

    /**
     * 解析Seconds
     * <P>
     *     主要解析： /
     * </P>
     *
     * @param sb
     *        翻译信息
     * @param tempCronArr
     *        临时Cron数组
     */
    private static void parserSeconds(StringBuilder sb, String[] tempCronArr) {
        // 解析Seconds 主要解析： /
        String hours = tempCronArr[2];
        String minutes = tempCronArr[1];
        String seconds = tempCronArr[0];

        if (Objects.equals(seconds, ASTERISK_WILDCARD)) {
            sb.setLength(0);
            sb.append("每").append(CRON_TIME_CN_LIST.get(0));
            return;
        }

        if (seconds.contains(CRON_SLASH)) {
            sb.append(appendTimeGapInfo(seconds, TIME_SCALE, 0));
        } else {
            if (StringUtils.isNumeric(seconds)){
                if (StringUtils.isNumeric(hours) && StringUtils.isNumeric(minutes)){
                    if (seconds.length() == 1){
                        sb.append("0").append(seconds);
                    }else {
                        sb.append(seconds);
                    }
                    return;
                }
            }
            sb.append(seconds).append("秒");
        }

    }

    /**
     * 解析周
     *
     * @param weekNum
     *        星期几数字
     * @return
     *        星期几中文
     */
    private static String judgeWeek(String weekNum) {
        String weekName = WeekEnum.matchName(weekNum);

        int weekNums = Integer.parseInt(weekNum);
        if (weekNums < 0 || weekNums > 7) {
            return "Cron表达式有误，dayOfWeek 数字应为1-7";
        }
        return StringUtils.isNotBlank(weekName) ? weekName : weekNum;
    }

    /**
     * 追加时间间隔信息
     *
     * @param timeStr
     *        时间字符串
     * @param rangeNum
     *        间隔数字
     * @param index
     *        索引
     * @return
     *        翻译后的信息
     */
    private static String appendTimeGapInfo(String timeStr, int rangeNum, int index) {

        StringBuilder sb = new StringBuilder();
        String[] timeArr = timeStr.split(CRON_SLASH);

        int startNum = Integer.parseInt(timeArr[0]);
        int gapNum = Integer.parseInt(timeArr[1]);
        int endNum = getEndTimeGap(startNum, gapNum, rangeNum);
        String timeUnit = CRON_TIME_CN_LIST.get(index);
        sb.append("从").append(startNum).append(timeUnit).append("开始到").append(endNum).append(timeUnit).append("范围内，每隔")
            .append(gapNum).append(timeUnit);
        return sb.toString();

    }

    /**
     * 获取最后的时间
     *
     * @param startNum
     *        开始
     * @param gapNum
     *        间隔
     * @param rangeNum
     *        最终
     * @return
     *        最后的时间
     */
    private static int getEndTimeGap(int startNum, int gapNum, int rangeNum) {

        int endNum = 0;
        for (int i = startNum; i < rangeNum; i = i + gapNum) {
            endNum = i;
        }
        return endNum;
    }

}
